/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package phd.dupenois.changeidentifiers.engine;

import phd.dupenois.changeidentifiers.ChangeIdentifierSet;
import phd.dupenois.changeidentifiers.dotpatterns.DotPattern;
import phd.dupenois.changeidentifiers.dotpatterns.DotPatternBuffer;
import phd.dupenois.changeidentifiers.engine.testing.Test;
import phd.dupenois.changeidentifiers.engine.testing.TestManager;
import phd.dupenois.changeidentifiers.engine.testing.TestingInitialisationException;
import phd.dupenois.changeidentifiers.engine.testing.TestingRecordException;
import phd.dupenois.changeidentifiers.engine.testing.TimeStep;
import phd.dupenois.changeidentifiers.footprints.Footprint;
import phd.dupenois.changeidentifiers.footprints.algorithms.FootprintAlgorithm;

/**
 *
 * @author Max
 */
public class Engine implements EngineInterface {

    private ChangeIdentifierSet changeIdentifierSet;
    private ChangeIdentifierApplicationInterface application;
    private EngineListener listener;
    private FootprintAlgorithm footprintAlgorithm;
    private DotPatternBuffer dotPatternBuffer;
    private Footprint currentFootprint;
    private DotPattern previousChangeCausingDotPattern;
    private TestManager testManager;
    private boolean running;
    private boolean run;
    private int pauseTime;
    private Test test;
    private String testTitle;
    private String testDescription;
    private long engineStartTime;
    private boolean first;

    public Engine(ChangeIdentifierApplicationInterface application, EngineListener listener) {
        this.application = application;
        this.listener = listener;
    }

    public void setupTestManager(TestManager testManager, String testTitle, String testDescription)
            throws TestingInitialisationException {
        testManager.initialise();
        this.testManager = testManager;
        this.testDescription = testDescription;
        this.testTitle = testTitle;
    }

    public void initialise(FootprintAlgorithm footprintAlgorithm,
            ChangeIdentifierSet changeIdentifierSet) {
        initialise(footprintAlgorithm, changeIdentifierSet, 0);
    }

    public void initialise(FootprintAlgorithm footprintAlgorithm,
            ChangeIdentifierSet changeIdentifierSet, int pauseTime) {
//        try {
        this.changeIdentifierSet = changeIdentifierSet;
        this.footprintAlgorithm = footprintAlgorithm;
        this.footprintAlgorithm.initialise();
        this.dotPatternBuffer = new DotPatternBuffer();
        if(this.isTesting()) this.dotPatternBuffer.initialiseTesting();
        this.pauseTime = pauseTime;
//            DotPattern dummyPattern = new DotPattern();
//            dummyPattern.addDot(new Dot(0, 0));
//            dummyPattern.addDot(new Dot(1, 0));
//            dummyPattern.addDot(new Dot(0, 1));
//            dummyPattern.addDot(new Dot(1, 1));
//            this.footprintAlgorithm.generateFootprint(dummyPattern);
//        } catch (Exception ex) {
//            ex.printStackTrace();
//        }
    }

    public void loadDotPattern(DotPattern dotPattern) {
        this.dotPatternBuffer.add(dotPattern);
    }


    private void runningCore(boolean withChangeIdentifiers){
        try {
            if (this.dotPatternBuffer.hasNext()) {
                if(this.first && withChangeIdentifiers){
                    this.engineStartTime = System.currentTimeMillis();
                    this.listener.startedEngine(engineStartTime);
                }
                DotPattern dotPattern = this.dotPatternBuffer.next();

                this.application.drawDotPattern(dotPattern);

                this.execute(dotPattern, withChangeIdentifiers);

                if (this.pauseTime > 0) {
                    try {
                        Thread.sleep(this.pauseTime);
                    } catch (InterruptedException ex) {
                    }
                }
            } else {
                this.application.dotPatternBufferIsEmpty();
                try {
                    Thread.sleep(0);
                } catch (InterruptedException ex) {
                }
            }
        } catch (RuntimeException ex) {
            this.application.exceptionThrown(ex);
            ex.printStackTrace();
        } catch (Exception ex) {
            this.application.exceptionThrown(ex);
            ex.printStackTrace();
        }
    }

    public void run() {
        this.running = true;
        this.run = true;
        if(isTesting()){
            test = new Test();
            test.setDescription(testDescription);
            test.setTitle(testTitle);
            test.setFootprintAlgorithmClass(this.footprintAlgorithm.getClass().getCanonicalName());
            test.setChangeIdentifierSet(changeIdentifierSet);
        }
        this.first = true;
        this.redrawCount = 0;
        while (this.run) {
            runningCore(true);
        }
        if(isTesting()) test.setRedrawCount(redrawCount);
        this.running = false;
        long timeTaken = System.currentTimeMillis() - this.engineStartTime;
        int withCIredrawCount = redrawCount;
        this.listener.finishedWithChangeIdentifiers(timeTaken, withCIredrawCount);
//        try{Thread.sleep(5000);}catch(InterruptedException ex){};
        if(isTesting()) {
            test.setStart(this.engineStartTime);
            test.setTimeTaken(timeTaken);
            this.first = true;
            this.running = true;
            this.run = true;
            this.dotPatternBuffer.switchBuffers();
            this.redrawCount = 0;
            long testStartTime = System.currentTimeMillis();
            this.listener.startedRunningTestsWithoutChangeIdentifiers(testStartTime);
            while (this.run) {
                runningCore(false);
            }
            test.setRedrawCountWithoutChangeIdentifiers(redrawCount);
            this.running = false;
            long testTimeTaken = System.currentTimeMillis() - testStartTime;
            test.setStartWithoutChangeIdentifiers(testStartTime);
            test.setTimeTakenWithoutChangeIdentifiers(testTimeTaken);
            this.listener.finishedRunningTestsWithoutChangeIdentifiers(testTimeTaken, redrawCount, testTimeTaken-timeTaken, redrawCount-withCIredrawCount);
            try{
                this.listener.startedRecordingTests();
                this.testManager.recordTest(test);
                this.listener.finishedRecordingTests();
            }catch(TestingRecordException ex){
                this.application.exceptionThrown(ex);
            }

        }
        this.listener.finishedEngine();
    }
    private int redrawCount;
    private void execute(DotPattern pattern, boolean withChangeIdentifiers) throws Exception {
        long startTime = System.currentTimeMillis();
        TimeStep timeStep = null;
        if(isTesting()) timeStep = new TimeStep(startTime, pattern);
        this.listener.startedExecution(startTime);
        boolean redraw = false;
        if(!this.first && withChangeIdentifiers){
//            long changeIdentifierStart = System.currentTimeMillis();
            redraw = this.changeIdentifierSet.redraw(currentFootprint, previousChangeCausingDotPattern, pattern);
//            System.out.println("+ Takes "+(System.currentTimeMillis()-changeIdentifierStart)+"ms to check cis");
            if(isTesting())timeStep.setChangeIdentifierSetSnapshot(this.changeIdentifierSet.getSnapshot());
        }else{
            redraw = true;
        }
        if (redraw) {
            if(isTesting()) timeStep.setRedraw(true);
            redrawCount++;
            this.previousChangeCausingDotPattern = pattern;
            long algTime = System.currentTimeMillis();
            this.listener.startedFootprintAlgorithm(this.footprintAlgorithm, algTime);
            this.currentFootprint = this.footprintAlgorithm.generateFootprint(pattern);
            long algTimeTaken = System.currentTimeMillis() - algTime;
//            System.out.println((withChangeIdentifiers?"+ With ":"- Without ")+"Takes "+(algTimeTaken)+"ms to run algorithm");
            this.listener.finishedFootprintAlgorithm(this.footprintAlgorithm, algTimeTaken);
//            if(!withChangeIdentifiers){
//                try{
//                    Thread.sleep(100);
//                }catch(InterruptedException ex){}
//            }
            this.application.drawFootprint(this.currentFootprint);
            if(isTesting()){
                timeStep.setAlgStarted(algTime);
                timeStep.setAlgTimeTaken(algTimeTaken);
            }
            this.first = false;
        }
        if(isTesting()) timeStep.setFootprint(currentFootprint);
        long timeTaken = System.currentTimeMillis() - startTime;
        this.listener.finishedExecution(timeTaken);
        if(isTesting()) {
            timeStep.setTimeTaken(timeTaken);
            if(withChangeIdentifiers){
                this.test.addTimeStep(timeStep);
            }else{
                this.test.addTimeStepWithoutChangeIdentifiers(timeStep);
            }
        }
    }

    public boolean isRunning() {
        return this.running;
    }

    public boolean isStopping() {
        return (this.running && !this.run);
    }

    public boolean isTesting() {
        return (this.testManager != null);
    }

    public TestManager getTestManager() {
        return this.testManager;
    }

    public void stop() {
        this.run = false;
    }
}
